package databasemanager

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"goGameWebServer/internal/common/config"
	"goGameWebServer/internal/common/logger"
	"goGameWebServer/internal/interfaces"
	"goGameWebServer/internal/register"
	"sync"

	"github.com/gogf/gf/v2/database/gdb"
)

type DatabaseManager struct {
	databases map[string]interfaces.IDataBase
}

func (dbManager *DatabaseManager) InitDatabases(ctx context.Context, options config.ServerOptions) {
	hasDefaultDatabase := false
	for _, databaseOption := range options.DatabaseOptions {
		databaseCreateFunc := register.GetDatabaseCreateFunc(databaseOption.Type)
		if databaseCreateFunc == nil {
			err := errors.New("database type not found")
			panic(err)
		}
		database, err := databaseCreateFunc(ctx, databaseOption)
		if err != nil {
			panic(err)
		}
		database.Init()
		dbManager.databases[databaseOption.GroupName] = database
		if databaseOption.GroupName == "default" {
			hasDefaultDatabase = true
		}
	}
	if !hasDefaultDatabase {
		panic(errors.New("default database not found"))
	}
}

func (dbManager *DatabaseManager) DataBase(groupName string) (interfaces.IDataBase, error) {
	if database, ok := dbManager.databases[groupName]; ok {
		return database, nil
	}
	logger.LoggerIns().Error(fmt.Sprintf("database %s not found", groupName))
	return nil, errors.New("database not found")
}

func (dbManager *DatabaseManager) DefaultDataBase() (interfaces.IDataBase, error) {
	return dbManager.DataBase("default")
}

func (dbManager *DatabaseManager) SaveDataWithMap(groupName string, data gdb.Map, tableNameOrStruct ...interface{}) (result sql.Result, err error) {
	database, err := dbManager.DataBase(groupName)
	if err != nil {
		return nil, err
	}
	return database.SaveDataWithMap(data, tableNameOrStruct...)
}

func (dbManager *DatabaseManager) SaveDataWithStruct(groupName string, data interface{}, tableNameOrStruct ...interface{}) (result sql.Result, err error) {
	database, err := dbManager.DataBase(groupName)
	if err != nil {
		return nil, err
	}
	return database.SaveDataWithStruct(data, tableNameOrStruct...)
}

func (dbManager *DatabaseManager) SaveMultiDataWithListMap(groupName string, data gdb.List, batch int, tableNameOrStruct ...interface{}) (result sql.Result, err error) {
	database, err := dbManager.DataBase(groupName)
	if err != nil {
		return nil, err
	}
	return database.SaveMultiDataWithListMap(data, batch, tableNameOrStruct...)
}

func (dbManager *DatabaseManager) InsertDataWithMap(groupName string, data gdb.Map, tableNameOrStruct ...interface{}) (result sql.Result, err error) {
	database, err := dbManager.DataBase(groupName)
	if err != nil {
		return nil, err
	}
	return database.InsertDataWithMap(data, tableNameOrStruct...)
}

func (dbManager *DatabaseManager) InsertDataWithStruct(groupName string, data interface{}, tableNameOrStruct ...interface{}) (result sql.Result, err error) {
	database, err := dbManager.DataBase(groupName)
	if err != nil {
		return nil, err
	}
	return database.InsertDataWithStruct(data, tableNameOrStruct...)
}

func (dbManager *DatabaseManager) InsertDataWithListMap(groupName string, data gdb.List, batch int, tableNameOrStruct ...interface{}) (result sql.Result, err error) {
	database, err := dbManager.DataBase(groupName)
	if err != nil {
		return nil, err
	}
	return database.InsertDataWithListMap(data, batch, tableNameOrStruct...)
}

func (dbManager *DatabaseManager) InsertDataWithMapAndGetId(groupName string, data gdb.Map, tableNameOrStruct ...interface{}) (lastInsertId int64, err error) {
	database, err := dbManager.DataBase(groupName)
	if err != nil {
		return -1, err
	}
	return database.InsertDataWithMapAndGetId(data, tableNameOrStruct...)
}

func (dbManager *DatabaseManager) InsertDataWithStructAndGetId(groupName string, data interface{}, tableNameOrStruct ...interface{}) (lastInsertId int64, err error) {
	database, err := dbManager.DataBase(groupName)
	if err != nil {
		return -1, err
	}
	return database.InsertDataWithStructAndGetId(data, tableNameOrStruct...)
}

func (dbManager *DatabaseManager) ReplaceDataWithMap(groupName string, data gdb.Map, tableNameOrStruct ...interface{}) (result sql.Result, err error) {
	database, err := dbManager.DataBase(groupName)
	if err != nil {
		return nil, err
	}
	return database.ReplaceDataWithMap(data, tableNameOrStruct...)
}

func (dbManager *DatabaseManager) ReplaceDataWithStruct(groupName string, data interface{}, tableNameOrStruct ...interface{}) (result sql.Result, err error) {
	database, err := dbManager.DataBase(groupName)
	if err != nil {
		return nil, err
	}
	return database.ReplaceDataWithStruct(data, tableNameOrStruct...)
}

func (dbManager *DatabaseManager) ReplaceDataWithListMap(groupName string, data gdb.List, batch int, tableNameOrStruct ...interface{}) (result sql.Result, err error) {
	database, err := dbManager.DataBase(groupName)
	if err != nil {
		return nil, err
	}
	return database.ReplaceDataWithListMap(data, batch, tableNameOrStruct...)
}

func NewDataBaseManager() *DatabaseManager {
	return &DatabaseManager{
		databases: make(map[string]interfaces.IDataBase),
	}
}

var databaseManagerInstance *DatabaseManager
var once sync.Once

func ManagerDatabase() *DatabaseManager {
	once.Do(func() {
		databaseManagerInstance = &DatabaseManager{
			databases: make(map[string]interfaces.IDataBase),
		}
	})
	return databaseManagerInstance
}
